Womit beschäftigen wir uns heute?
Zur Erinnerung: Die Hilfe zu einer Funktion lässt sich aufrufen, indem man dem Funktionsnamen ein Fragezeichen voranstellt:
?helpUnd mit ?? lässt sich nach vagen Begriffen suchen:
??randomforestBenötigte Pakete lassen sich zwar grundsätzlich jederzeit einbinden, es bietet sich aber an, dies zu Beginn eines Skripts zu tun, um den Überblick zu behalten.
library(tidyverse)
library(readxl) # für Excel-Tabellen
library(psych)
library(skimr)
library(corpora)
library(ggcorrplot)Im Vorbereitungsskript ging es bereits um Zahlen, logische Werte (TRUE/FALSE), Zeichenketten/Strings (R-Bezeichnung: character) und Vektoren.
Faktoren sind speziell für kategoriale Daten gedacht. Ein Faktor hat eine feste Menge von Levels/möglichen Ausprägungen:
geschlecht <- c(rep("m", 10), rep("w", 20)) |>
sample() |> # durchmischen, damit es nach echten Daten aussieht
factor()
geschlecht## [1] w w m m w w w w w m w w w w w w m w m m w m w m w w w w m m
## Levels: m w
Um die Namen der Ausprägungen/Levels zu verändern, können wir levels() verwenden:
levels(geschlecht) <- c("männlich", "weiblich")
geschlecht## [1] weiblich weiblich männlich männlich weiblich weiblich weiblich weiblich
## [9] weiblich männlich weiblich weiblich weiblich weiblich weiblich weiblich
## [17] männlich weiblich männlich männlich weiblich männlich weiblich männlich
## [25] weiblich weiblich weiblich weiblich männlich männlich
## Levels: männlich weiblich
Wenn man einen Faktor verwendet, um unterschiedliche Gruppen zu definieren, ist es sinnvoll, die Kontrollgruppe (oder die Gruppe, die als Vergleichsbasis dient), als erste aufzuführen. (Weil viele Funktionen die erste Gruppe als Referenzgruppe nehmen.)
Am einfachsten geht das vielleicht mit der Funktion relevel():
f <- factor(c("a", "a", "c", "b", "b", "b", "d"))
relevel(f, "b")## [1] a a c b b b d
## Levels: b a c d
Mit der Funktion fct_relevel() (aus tidyverse/forcats) kann man die Levels beliebig verschieben:
fct_relevel(f, "b") # wie relevel()## [1] a a c b b b d
## Levels: b a c d
fct_relevel(f, "b", after = 2)## [1] a a c b b b d
## Levels: a c b d
fct_relevel(f, "a", after = Inf) # ans Ende## [1] a a c b b b d
## Levels: b c d a
(In dem Paket befinden sich noch einige weitere sehr praktische Funktionen zum Umgang mit Faktoren, z.B. fct_lump() oder fct_expand().)
Für ordinalskalierte Daten können wir statt factor() die Funktion ordered() verwenden, um R mitzuteilen, dass die Daten eine Rangordnung haben:
bewertung <- c(rep("stimme voll zu", 3),
rep("stimme eher zu", 6),
rep("weder noch", 8),
rep("stimme eher nicht zu", 4),
rep("stimme gar nicht zu", 1)) |>
sample() # Unordnung
ordered(bewertung)## [1] weder noch stimme eher nicht zu stimme gar nicht zu
## [4] stimme eher zu weder noch stimme eher zu
## [7] weder noch stimme eher nicht zu stimme eher zu
## [10] stimme eher nicht zu stimme voll zu stimme eher zu
## [13] stimme eher zu weder noch stimme eher zu
## [16] stimme voll zu stimme eher nicht zu weder noch
## [19] weder noch stimme voll zu weder noch
## [22] weder noch
## 5 Levels: stimme eher nicht zu < stimme eher zu < ... < weder noch
Ohne weitere Argumente sortiert die Funktionen die Ausprägungen allerdings bloß alphabetisch – also müssen wir die Reihenfolge vorgeben:
bewertung <- ordered(bewertung, levels = c("stimme gar nicht zu",
"stimme eher nicht zu",
"weder noch",
"stimme eher zu",
"stimme voll zu"))
bewertung## [1] weder noch stimme eher nicht zu stimme gar nicht zu
## [4] stimme eher zu weder noch stimme eher zu
## [7] weder noch stimme eher nicht zu stimme eher zu
## [10] stimme eher nicht zu stimme voll zu stimme eher zu
## [13] stimme eher zu weder noch stimme eher zu
## [16] stimme voll zu stimme eher nicht zu weder noch
## [19] weder noch stimme voll zu weder noch
## [22] weder noch
## 5 Levels: stimme gar nicht zu < stimme eher nicht zu < ... < stimme voll zu
Damit lässt sich der geordnete Faktor nun übrigens auch nach Größe sortieren (statt nur alphabetisch):
sort(bewertung)## [1] stimme gar nicht zu stimme eher nicht zu stimme eher nicht zu
## [4] stimme eher nicht zu stimme eher nicht zu weder noch
## [7] weder noch weder noch weder noch
## [10] weder noch weder noch weder noch
## [13] weder noch stimme eher zu stimme eher zu
## [16] stimme eher zu stimme eher zu stimme eher zu
## [19] stimme eher zu stimme voll zu stimme voll zu
## [22] stimme voll zu
## 5 Levels: stimme gar nicht zu < stimme eher nicht zu < ... < stimme voll zu
Wir können Daten auch in einen anderen Datentyp umwandeln:
as.integer(41.728)## [1] 41
as.character(.345)## [1] "0.345"
as.double("5.23")## [1] 5.23
as.integer(c(TRUE, FALSE, TRUE, TRUE, FALSE))## [1] 1 0 1 1 0
R kann mit Daten in verschiedensten Formaten umgehen; wir wollen uns hier aber auf typische Tabellenformate beschränken.
Tabellen sind im Grunde genommen Listen von Vektoren gleicher Länge (wenn dabei alle Daten numerisch sind, können wir anstelle einer Tabelle auch eine Matrix verwenden).
Tabellen lesen wir normalerweise aus Dateien ein. R bevorzugt das CSV-Format (comma-separated values/character-separated values), mit geeigneten Paketen lassen sich aber auch die Dateiformate von Excel (z.B. .xslx), SPSS usw. einlesen.
Zu sehr ins Detail können wir leider nicht gehen. Wer das tun möchte, kann sich aber bspw. diesen DataCamp-Kurs ansehen: https://learn.datacamp.com/courses/importing-data-in-r-part-1
R und einige Pakete liefern netterweise gleich einige Datensätze zu Testzwecken mit, sodass wir uns direkt ansehen können, wie so eine Tabelle aussieht:
mtcars # im "data.frame"-Formatmpg # im "tibble"-Format (tidyverse/tibble)An beiden Beispielen sehen wir, dass Tabellen aus Zeilen (rows) und Spalten (columns) bestehen. Die erste Zeile ist üblicherweise die Kopfzeile (header), in der die Namen der Spalten stehen. Bei einem tibble wird direkt darunter für jede Spalte ausgegeben, welchen Datentyp sie hat:
Volle Liste: https://tibble.tidyverse.org/articles/types.html
Ein “data.frame”-Objekt lässt sich in ein “tibble” umwandeln:
as_tibble(mtcars)Nun werden nicht mehr alle Zeilen auf der Konsole ausgegeben. Das ist normalerweise wünschenswert. Man kann aber mit der Funktion print Zeilenanzahl und maximale Tabellenbreite (in Zeichen) angeben:
as_tibble(mtcars) |>
print(n = 20, width = 40) # "width = Inf" für alle Spalten ## # A tibble: 32 × 11
## mpg cyl disp hp drat wt
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 21 6 160 110 3.9 2.62
## 2 21 6 160 110 3.9 2.88
## 3 22.8 4 108 93 3.85 2.32
## 4 21.4 6 258 110 3.08 3.22
## 5 18.7 8 360 175 3.15 3.44
## 6 18.1 6 225 105 2.76 3.46
## 7 14.3 8 360 245 3.21 3.57
## 8 24.4 4 147. 62 3.69 3.19
## 9 22.8 4 141. 95 3.92 3.15
## 10 19.2 6 168. 123 3.92 3.44
## 11 17.8 6 168. 123 3.92 3.44
## 12 16.4 8 276. 180 3.07 4.07
## 13 17.3 8 276. 180 3.07 3.73
## 14 15.2 8 276. 180 3.07 3.78
## 15 10.4 8 472 205 2.93 5.25
## 16 10.4 8 460 215 3 5.42
## 17 14.7 8 440 230 3.23 5.34
## 18 32.4 4 78.7 66 4.08 2.2
## 19 30.4 4 75.7 52 4.93 1.62
## 20 33.9 4 71.1 65 4.22 1.84
## # … with 12 more rows, and 5 more
## # variables: qsec <dbl>, vs <dbl>,
## # am <dbl>, gear <dbl>, carb <dbl>
Um eine Tabelle als tibble zu erzeugen:
tibble(
Rang = 1:6,
Frequenz = c(555503423, 525757950, 258037531, 254258892, 244546490,
135861000),
Freq_pMT = Frequenz / 11660894000 * 1e6,
Token = c(",", ".", "und", "die", "der", "in")
)(Dies sind die ersten sechs Zeilen der Frequenzliste des DECOW14, einer riesigen deutschen Sammlung von Texten aus dem Web.)
Schön daran: Einmal definierte Spalten lassen sich direkt für Berechnungen für weitere Spalten verwenden (siehe Frequenz).
Um auf eine Tabelle als Objekt zugreifen zu können, gehen wir genauso vor wie auch bei Einzelwerten und Vektoren:
decow <- tibble(
Rang = 1:6,
Frequenz = c(555503423, 525757950, 258037531, 254258892, 244546490,
135861000),
Freq_pMT = Frequenz / 11660894000 * 1e6,
Token = c(",", ".", "und", "die", "der", "in")
)Die Tabelle sollte jetzt als Objekt unter “Environment” auftauchen. Ein Klick darauf zeigt uns die ganze Tabelle (das gleiche bewirkt auch die Funktion View()).
Wer schon Erfahrung mit R hat, kennt wahrscheinlich schon read.table(), read.csv(), read.csv2() usw. Alternativ kann man Dateien aber auch direkt im “tibble”-Format öffnen. (Und für sehr große Dateien bietet sich das Paket “data.table” mit der Funktion fread() an, die den Einleseprozess enorm beschleunigen kann.)
Welche Funktion man am besten verwendet, hängt vom Dateiformat und der Formatierung ab (also z.B. davon, mit welchen Zeichen Werte voneinander getrennt sind, was als Dezimaltrennzeichen verwendet wird usw.).
?read_delim # tidyverse/readr
?read.table # Basis-RFür CSV-Dateien im europäischen Format (Semikolon als Trennzeichen, Komma als Dezimaltrennzeichen) verwenden wir read_csv2():
complexity <- read_csv2("../data/sentence_complexity.csv")## ℹ Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
## Rows: 1000 Columns: 11
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ";"
## chr (1): sentence
## dbl (10): id, mean_word_length, mean_syllables, max_syllables, lexical_densi...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
complexityMit teilweise spezifizierten Datentypen:
complexity <- read_csv2("../data/sentence_complexity.csv",
col_types = cols(id = "i",
max_syllables = "i",
tokens = "i",
max_dependency_distance = "i",
fin_verbs = "i"))## ℹ Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
complexityAls Argument übergibt man der Funktion hier einen Dateipfad. Dieser kann entweder absolut angegeben werden (unter Windows also beginnend mit dem Laufwerksbuchstaben) oder relativ zum aktuellen Arbeitsverzeichnis oder – in .Rmd-Dateien – relativ zum Speicherort des aktuellen Skripts (wie hier geschehen).
Alternativ kann man die Funktion file.choose() verwenden, um über ein Dialogfenster eine Datei auszuwählen:
read_csv2(file.choose())Neben read_csv2() gibt es read_csv() für das amerikanische Format (Komma als Trennzeichen, Punkt als Dezimaltrennzeichen), read_tsv() für Dateien, in denen Tabulatoren als Trennzeichen verwendet werden, und read_delim() als übergeordnete Funktion, bei der man selbst angeben kann, welche Zeichen als Trennzeichen usw. verwendet werden.
In RStudio kann man über File -> Import Dataset verschiedene Möglichkeiten aufrufen, Tabellendateien zu öffnen (mit Vorschau und verschiedenen Optionen):
Klickt man nach Auswahl passender Optionen dann auf “Import”, kann man sich den verwendeten R-Befehl auch aus der Konsole in sein eigenes Skript kopieren.
Ein Beispiel zum Öffnen einer Excel-Datei:
complexity <- read_excel("../data/sentence_complexity.xlsx", sheet = 1)
complexityWer eigene Daten dabei hat, sollte spätestens an dieser Stelle einmal ausprobieren, sie zu öffnen. Erfahrungsgemäß tauchen dabei immer irgendwelche Probleme auf …
Um Tabellen zu speichern, gibt es analog zu den diversen read_-Funktionen passende write_-Funktionen, denen man zuerst den Namen des Objekts, dann den gewünschten Speicherpfad und ggf. weitere Parameter übergibt:
write_csv2(complexity, file.choose())
write_csv2(complexity, "../data/sentence_complexity.csv")Um auf einzelne Spalten (meist einzelne statistische Variablen) zuzugreifen, geben wir den Namen der Tabelle ein, gefolgt von einem Dollarzeichen und dem Namen der Spalte. Wir erhalten einen Vektor:
decow$Token## [1] "," "." "und" "die" "der" "in"
decow$Frequenz## [1] 555503423 525757950 258037531 254258892 244546490 135861000
Um auf Teile der Tabelle zuzugreifen, kann man eckige Klammern verwenden. Das funktioniert genauso wie mit Vektoren, nur dass wir diesmal zwei Positionen angeben müssen: Zeile und Spalte.
decow[2, 3] # zweite Zeile, dritte Spaltedecow[3, 2:4] # dritte Zeile, Spalten 2 bis 4decow[3,] # dritte Zeile, alle Spalten (Komma nicht vergessen!)decow[, 3] # dritte Spalte (geht auch ohne Komma, ist dann aber verwirrender ...)decow[c(1, 3),] # 1 und dritte Zeile, alle SpaltenWeil man die richtige Reihenfolge erfahrungsgemäß gerne einmal vergisst, hilft vielleicht ein alberner Merkspruch:
Erst die Zeile, dann die Spalt’,
Bis es auch der letzte schnallt.
(Bessere Vorschläge sind jederzeit willkommen.)
Zur Auswahl bestimmter Spalten gibt es auch noch select():
decow |>
select(Freq_pMT, Token)decow |>
select(Frequenz:Token)Umbenennen kann man die Variablen dabei auch:
decow |>
select(Frequenz_pMT = Freq_pMT, Token)(Achtung, das ist erst einmal nur die Ausgabe der Funktion, das Objekt decow bleibt unverändert – sofern wir ihm die Ausgabe nicht zuweisen.)
Wenn es nur ums Umbenennen geht, man aber eigentlich die übrigen Spalten beibehalten möchte, ist rename() praktischer:
decow |>
rename(Frequenz_pMT = Freq_pMT)select() ist auch praktisch, wenn man bloß die Reihenfolge der Spalten verändern möchte:
decow |>
select(Token, everything()) # everything(): kleine HilfsfunktionOft will man Teile einer Tabelle nicht anhand ihrer Position auswählen, sondern anhand bestimmter Kriterien, die die enthaltenen Daten erfüllen müssen. Dafür gibt es die Funktion filter().
Welche Sätze im eben eingelesenen Datensatz wurden z.B. als besonders komplex bewertet?
complexity |>
filter(mean_complexity > 6)Wenn mehrere Bedingungen gleichzeitig erfüllt sein sollen, kann man sie mit Kommata abtrennen:
complexity |>
filter(tokens >= 30, mean_word_length >= 5)Ein logisches UND funktioniert aber ebenso:
complexity |>
filter(tokens >= 30 & mean_word_length >= 5)Logische Operatoren, die hier verwendet werden können, kamen schon in der Datei zur Vorbereitung vor. Zur Übung:
Manchmal will man einer bestehenden Tabelle weitere Spalten anhängen. Diese Vektoren müssen natürlich dieselbe Länge haben wie die anderen Spalten der Tabelle.
Die einfachste Möglichkeit sieht m.E. so aus:
complexity$length_chars <- str_length(complexity$sentence) # Satzlänge in Zeichen
complexityAußerdem gibt es die Funktion mutate(), mit der man auch gleich mehrere Spalten auf einmal anfügen und Berechnungen mit den bestehenden Spalten anstellen kann:
complexity |>
select(sentence) |>
mutate(length_chars = str_length(sentence),
length_chars_centered = length_chars - mean(length_chars))Um Spalten zu löschen, gibt es mehrere Möglichkeiten.
Einfach ist es, einfach alle Spalten außer der zu entfernenden auszuwählen:
complexity |>
select(-id)Das Ergebnis muss man dann natürlich wieder einem Objekt zuweisen.
Alternativ kann man auch einfach eine ganze Spalte auf NULL setzen, dann verschwindet sie aus dem ursprünglichen Objekt:
complexity$length_chars <- NULL
complexitycomplexity$length_chars <- str_length(complexity$sentence) # wieder anfügenWill man sich die Zeilen einer Tabelle in anderer Reihenfolge anzeigen lassen, kann man die Funktion arrange() verwenden:
complexity |>
arrange(desc(mean_complexity))desc() gibt an, dass die Spalte absteigend sortiert werden soll.
Man kann zusätzlich auch nach weiteren Spalten sortieren:
complexity |>
arrange(fin_verbs, tokens)complexity |>
arrange(desc(max_syllables), desc(fin_verbs), desc(tokens))Sollte eine Spalte fehlende Werte (NA) enthalten, werden
diese stets ans Ende gesetzt, egal, ob man auf- oder absteigend
sortiert.
In der Datenwildnis bekommt man es früher oder später mit unterschiedlichen Präsentationsformen von Tabellen zu tun:
Da beide Formate gebräuchlich sind und viele Funktionen in R ein bestimmtes Eingabeformat erwarten (überwiegend das Langformat), sollte man wissen, wie man Tabellen von einem ins andere umwandeln kann. Im Tidyverse gibt es dafür die beiden Funktionen pivot_longer() und pivot_wider().
Zur Veranschaulichung nutzen wir Häufigkeitsdaten ausgewählter
Substantive im geschriebenen und gesprochenen Teil des British National
Corpus (BNC; siehe ?BNCcomparison):
BNCcomparison |> as_tibble()Da die Spalten written und spoken beide
Häufigkeiten enthalten, könnten wir diese Werte in eine einzige Spalte
packen. Eine weitere müsste dann bloß die Modalität
(geschrieben/gesprochen) angeben.
Um vom Breit- ins Langformat zu wechseln, benutzen wir pivot_longer():
BNC_long <- BNCcomparison |>
pivot_longer(cols = written:spoken,
names_to = "modality",
values_to = "frequency")
BNC_longFür die umgekehrte Richtung entsprechend pivot_wider():
BNC_long |>
pivot_wider(names_from = "modality",
values_from = "frequency")In einer Vignette gibt es noch jede Menge weiterer Beispiele dazu:
vignette("pivot")Extrem mächtig sind in Kombination die beiden Funktionen group_by() zur Aufspaltung eines Datensatzes in Gruppen und summarise() zur Anwendung beliebiger Operationen auf diese Gruppen (Summenbildung, Berechnung von Mittelwerten oder Standardabweichungen, …):
complexity |>
group_by(fin_verbs) |>
summarise(
Anzahl = n(),
Mittlere_Satzlaenge = mean(tokens),
Mittlere_Komplexitaet = mean(mean_complexity)
)Simulierte Daten mit Häufigkeit des Alkoholkonsums diverser Personen, ihrem Alter, Geschlecht und den erreichten Punkten in einem Wissenstest:
punkte <- read_csv2("../data/punkte.csv")## ℹ Using "','" as decimal and "'.'" as grouping mark. Use `read_delim()` for more control.
## Rows: 100 Columns: 4
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ";"
## chr (1): Geschlecht
## dbl (3): Alter, Alkoholkonsum, Punkte
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Mittelwert, Median, Spannweite, Interquartilsabstand und Standardabweichung der Punkte:
mean(punkte$Punkte)## [1] 49.92
median(punkte$Punkte)## [1] 50
range(punkte$Punkte)## [1] 19 91
IQR(punkte$Punkte)## [1] 37
sd(punkte$Punkte)## [1] 20.21834
describe() aus dem psych-Paket:
describe(punkte)describeBy() aus demselben Paket:
describeBy(punkte, punkte$Geschlecht)##
## Descriptive statistics by group
## group: männlich
## vars n mean sd median trimmed mad min max range skew
## Alter 1 50 17.74 3.75 18 17.90 4.45 10 24 14 -0.32
## Geschlecht* 2 50 1.00 0.00 1 1.00 0.00 1 1 0 NaN
## Alkoholkonsum 3 50 4.32 2.61 5 4.40 1.48 0 9 9 -0.44
## Punkte 4 50 58.58 17.50 63 60.23 16.31 19 85 66 -0.70
## kurtosis se
## Alter -0.72 0.53
## Geschlecht* NaN 0.00
## Alkoholkonsum -0.97 0.37
## Punkte -0.64 2.47
## ------------------------------------------------------------
## group: weiblich
## vars n mean sd median trimmed mad min max range skew
## Alter 1 50 13.86 3.99 13 13.32 2.97 9 24 15 1.03
## Geschlecht* 2 50 1.00 0.00 1 1.00 0.00 1 1 0 NaN
## Alkoholkonsum 3 50 1.82 2.41 0 1.43 0.00 0 8 8 1.03
## Punkte 4 50 41.26 19.15 35 39.10 17.79 20 91 71 0.79
## kurtosis se
## Alter 0.32 0.56
## Geschlecht* NaN 0.00
## Alkoholkonsum -0.24 0.34
## Punkte -0.57 2.71
Manuelle Überblicksstatistiken
punkte |>
group_by(Geschlecht) |>
summarise(
Alter_mean = mean(Alter),
Alter_sd = sd(Alter),
Punkte_mean = mean(Punkte),
Punkte_sd = sd(Punkte),
Alkoholkonsum_mean = mean(Alkoholkonsum),
Alkoholkonsum_sd = sd(Alkoholkonsum)
)skim() aus dem skimr-Paket:
skim(punkte)| Name | punkte |
| Number of rows | 100 |
| Number of columns | 4 |
| _______________________ | |
| Column type frequency: | |
| character | 1 |
| numeric | 3 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| Geschlecht | 0 | 1 | 8 | 8 | 0 | 2 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| Alter | 0 | 1 | 15.80 | 4.32 | 9 | 12.0 | 16 | 19.0 | 24 | ▇▆▆▅▃ |
| Alkoholkonsum | 0 | 1 | 3.07 | 2.80 | 0 | 0.0 | 3 | 6.0 | 9 | ▇▂▅▃▁ |
| Punkte | 0 | 1 | 49.92 | 20.22 | 19 | 31.5 | 50 | 68.5 | 91 | ▇▅▅▇▂ |
Auch nach Gruppen möglich:
punkte |>
group_by(Geschlecht) |>
skim()| Name | group_by(punkte, Geschlec… |
| Number of rows | 100 |
| Number of columns | 4 |
| _______________________ | |
| Column type frequency: | |
| numeric | 3 |
| ________________________ | |
| Group variables | Geschlecht |
Variable type: numeric
| skim_variable | Geschlecht | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Alter | männlich | 0 | 1 | 17.74 | 3.75 | 10 | 15.25 | 18 | 20.75 | 24 | ▃▃▇▇▅ |
| Alter | weiblich | 0 | 1 | 13.86 | 3.99 | 9 | 11.00 | 13 | 16.00 | 24 | ▇▅▃▁▂ |
| Alkoholkonsum | männlich | 0 | 1 | 4.32 | 2.61 | 0 | 2.25 | 5 | 6.00 | 9 | ▅▂▆▇▂ |
| Alkoholkonsum | weiblich | 0 | 1 | 1.82 | 2.41 | 0 | 0.00 | 0 | 3.00 | 8 | ▇▂▁▂▁ |
| Punkte | männlich | 0 | 1 | 58.58 | 17.50 | 19 | 48.50 | 63 | 72.75 | 85 | ▃▂▃▇▆ |
| Punkte | weiblich | 0 | 1 | 41.26 | 19.15 | 20 | 25.00 | 35 | 53.75 | 91 | ▇▃▁▂▁ |
Für fehlende Werte gibt es in R den speziellen Wert
NA:
NA## [1] NA
zahlen <- c(13, 38.2, 8, NA, 98.21, NA, 8.15); zahlen## [1] 13.00 38.20 8.00 NA 98.21 NA 8.15
c("a", NA, "z", "e")## [1] "a" NA "z" "e"
Mehr Informationen:
?"NA"Will man mit Vektoren rechnen, die NA-Werte enthalten, ist das Ergebnis in der Regel NA:
mean(zahlen)## [1] NA
sd(zahlen)## [1] NA
Viele Funktionen erlauben aber den optionalen Parameter na.rm, den man auf TRUE setzen kann, um fehlende Werte zu ignorieren:
mean(zahlen, na.rm = TRUE)## [1] 33.112
sd(zahlen, na.rm = TRUE)## [1] 38.47676
Daneben gibt es (u.a.) auch die Funktion na.omit(), die alle fehlenden Werte aus einem Vektor oder alle Zeilen(!) aus einer Tabelle, in denen fehlende Werte vorkommen, entfernt.
Die Anwendung auf Tabellen will also gut überlegt sein – u.U. wirft man damit verwertbare Daten weg!
zahlen <- na.omit(zahlen)
mean(zahlen)## [1] 33.112
test <- tibble(
Geschlecht = c("männlich", "weiblich", NA, "weiblich") |> factor(),
Punkte = c(10, 12, 11, 9)
)
testtest |>
summarise(mean = mean(Punkte))test |>
na.omit() |>
summarise(mean = mean(Punkte))Daneben gibt es noch die Tidyverse-Funktion drop_na(), mit der alle Zeilen entfernt werden, die in den angegebenen Spalten fehlende Werte enthalten:
test |>
drop_na() # wie na.omit(): alle Spalten berücksichtigttest |>
drop_na(Geschlecht) # -> Zeile 3 wird entfernttest |>
drop_na(Punkte) # -> Zeile 3 wird nicht entferntWeitere nützliche Funktionen aus dem Tidyverse sind z.B. replace_na()
(um in bestimmten Spalten z.B. alle NA-Werte auf 0 zu
setzen) oder fill().
Eine gute Einführung in die Visualisierung von Daten mit ggplot2/Tidyverse gibt es unter: http://r4ds.had.co.nz/data-visualisation.html.
ggplot2 setzt eine geschichtete “Graphikgrammatik” um (grammar of graphics). Auf den ersten Blick wirkt das womöglich verwirrend, weil sich die Syntax etwas von Standard-R unterscheidet. Eine statistische Graphik besteht in diesem Modell aus der Zuordnung (mapping) von Variablen des Datensatzes (data) zu ästhetischen Attributen (aes) geometrischer Objekte (geom).
data: Datensatz, der die Variablen enthält, die
graphisch dargestellt werden sollengeom: Art geometrischer Objekte in der Graphik, z.B.
Linien, Punkte oder Säulenaes: ästhetische Attribute der geometrischen Objekte,
z.B. die Position im Koordinatensystem (x, y), Farbe oder Form. Diese
Attribute werden Variablen des Datensatzes zugeordnet.(Vergleiche https://moderndive.com/2-viz.html)
Ein Säulendiagramm mit ggplot():
ggplot(data = complexity) + # Datensatz angeben
geom_bar(mapping = aes(x = tokens)) + # Welche Darstellung, welche Variable wohin?
labs(title = "Satzlängenverteilung", # Titel über der Graphik
x = "Satzlänge", # Beschriftung der x-Achse
y = "Häufigkeit") # Beschriftung der y-Achsecomplexity |> # sehr üblich: Datensatz per Pipe an ggplot() übergeben -- so kann man auch noch Funktionen zwischenschalten
mutate(fin_verbs = factor(fin_verbs)) |> # fin_verbs für die Graphik zu Faktor machen
ggplot(mapping = aes(x = tokens, fill = fin_verbs)) + # mapping hier gilt für alle folgenden Funktionen
geom_bar() +
labs(title = "Satzlängenverteilung",
x = "Satzlänge",
y = "Häufigkeit",
fill = "Finite Verben")Um ggf. die Anordnung der Säulen zu ändern, müssen wir wissen, wie ggplot sie standardmäßig sortiert: - wenn das Objekt eine Zahl ist, nach Größe - wenn das Objekt ein Faktor ist, nach Reihenfolge der Ausprägungen - wenn das Objekt ein Vektor aus Zeichenketten (characters) ist, nach Alphabet
Liegen die Häufigkeiten direkt vor (wie in unserer oben erstellten
Tabelle decow), lässt sich geom_col() verwenden. Man muss
dann lediglich auch angeben, aus welcher Spalte die Werte auf der
y-Achse kommen sollen:
decow |>
mutate(Token = fct_inorder(Token)) |> # damit die Reihenfolge der Säulen stimmt
ggplot(mapping = aes(x = Token, y = Freq_pMT)) +
geom_col() +
labs(title = "Die häufigsten Tokens im DECOW14",
y = "Häufigkeit pro Million Tokens")Um das Erscheinungsbild zu ändern, lassen sich auch vordefinierte Themes verwenden (und ggf. noch weiter anpassen). Eine Übersicht gibt es hier: https://ggplot2.tidyverse.org/reference/ggtheme.html Weitere Themes lassen sich über separate Pakete installieren, z.B.:
decow |>
mutate(Token = fct_inorder(Token)) |>
ggplot(mapping = aes(x = Token, y = Freq_pMT)) +
geom_col() +
labs(title = "Die häufigsten Tokens im DECOW14",
y = "Häufigkeit pro Million Tokens") +
theme_bw()Wenn man am Ende coord_flip() anfügt, kann man aus einem Säulendiagramm übrigens ganz einfach ein Balkendiagramm machen.
complexity |>
ggplot(aes(mean_word_length)) +
geom_histogram() +
labs(title = "Verteilung mittlerer Wortlängen über die Sätze",
x = "Mittlere Wortlänge in Zeichen",
y = "Häufigkeit")## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Die Gestalt des Histogramms hängt sehr von der Anzahl der Säulen (bins) ab – beim Vergleich unterschiedlicher Histogramme muss man also etwas aufpassen.
Man kann die Balkenzahl selbst festlegen oder eine bestimmte Methode zur Berechnung verwenden:
complexity |>
ggplot(aes(mean_word_length)) +
geom_histogram(bins = 10) +
labs(title = "Verteilung mittlerer Wortlängen über die Sätze",
x = "Mittlere Wortlänge in Zeichen",
y = "Häufigkeit")Ideal für Gruppenvergleiche
punkte |>
ggplot(aes(x = Geschlecht,
y = Alter)) +
geom_boxplot() +
labs(title = "Stichprobenzusammensetzung")punkte |>
ggplot(aes(x = Geschlecht,
y = Alter)) +
geom_violin() +
labs(title = "Stichprobenzusammensetzung")complexity |>
ggplot(aes(x = tokens, y = mean_complexity)) +
geom_point(alpha = .6) +
scale_x_continuous(trans = "log10") +
geom_smooth() +
geom_smooth(method = "lm", colour = "red") +
labs(title = "Zusammenhang zwischen Satzlänge und Komplexität",
x = "Satzlänge (logarithmiert)",
y = "Komplexität")## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
## `geom_smooth()` using formula 'y ~ x'
Man kann in einen solchen Plot natürlich auch noch mehr Informationen packen:
complexity |>
ggplot(aes(x = tokens,
y = mean_complexity,
size = max_dependency_distance,
colour = fin_verbs)) +
geom_point(alpha = .6) +
scale_x_continuous(trans = "log10") +
labs(title = "Zusammenhang zwischen Satzlänge und Komplexität",
x = "Satzlänge (logarithmiert)",
y = "Komplexität",
colour = "Finite Verben",
size = "Max. Dependenzabstand")complexity |>
select(mean_word_length:mean_band_subtlex, length_chars, mean_complexity) |>
cor() |>
ggcorrplot(type = "lower",
outline.color = "white",
lab = TRUE)Eine sehr schöne Liste von Graphiken, die sich mit ggplot erzeugen
lassen (samt Code) gibt es hier:
http://r-statistics.co/Top50-Ggplot2-Visualizations-MasterList-R-Code.html